import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import { CallerMethod, PromiserCaller } from "./typings";

export class Caller<ProtoGrpcType> {
  protoDescriptor: ProtoGrpcType;
  constructor(protoPath: string) {
    const packageDefinition = protoLoader.loadSync(protoPath, {
      keepCase: true,
      longs: String,
      enums: String,
      defaults: true,
      oneofs: true,
    });

    this.protoDescriptor = grpc.loadPackageDefinition(
      packageDefinition
    ) as unknown as ProtoGrpcType;
  }

  link<T extends grpc.Client>(
    getClient: (
      p: ProtoGrpcType
    ) => new (...args: ConstructorParameters<typeof grpc.Client>) => T,
    address: string,
    options?: grpc.ClientOptions
  ): PromiserCaller<Omit<T, keyof grpc.Client>> {
    const Client = getClient(this.protoDescriptor);

    const service = new Client(
      address,
      grpc.credentials.createInsecure(),
      options
    );
    const caller: any = {};
    for (const key in service) {
      if (service[key] instanceof Function) {
        caller[key] = (request: any): Promise<any> => {
          return new Promise((resolve, reject) => {
            (service[key] as unknown as CallerMethod<unknown, unknown>)(
              request,
              (err, reply) => {
                err && reject(err);
                resolve(reply);
              }
            );
          });
        };
      }
    }
    return caller;
  }
}
